﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.IO;
using ConfigGen.Core.ConfigurationSettings;
using ConfigGen.Core.TemplateLoader;
using ConfigGen.Core.TemplateProcessing;
using ConfigGen.Core.TokenHandling;
using Moq;
using NUnit.Framework;

namespace ConfigGen.Core.Tests
{
    [TestFixture]
    public class SingleFileGeneratorTests
    {
        [Test]
        public void Ctor_NullTokenLocatorThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var ex = Assert.Throws<ArgumentNullException>(() => new SingleFileGenerator(null, mockTokenReplacer.Object), "should throw");
            Assert.AreEqual("tokenLocator", ex.ParamName, "Incorrect param name");
        }

        [Test]
        public void Ctor_NullTokenReplacerThrows()
        {
            var mockTokenLocator = new Mock<ITokenLocator>();
            var ex = Assert.Throws<ArgumentNullException>(() => new SingleFileGenerator(mockTokenLocator.Object, null), "should throw");
            Assert.AreEqual("tokenReplacer", ex.ParamName, "Incorrect param name");
        }

        [Test]
        public void Initialise_NullCurrerntMachineThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            var ex = Assert.Throws<ArgumentNullException>(()=>
                    itemUnderTest.Initialise(
                        null, 
                        new TokenCounts(), 
                        new TemplatePreferences()), "Should have thrown");
            Assert.AreEqual("currentMachine", ex.ParamName, "incorrect param name");
        }

        [Test]
        public void Initialise_NullTokenCountsThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            var ex = Assert.Throws<ArgumentNullException>(() =>
                    itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]), 
                        null,
                        new TemplatePreferences()), "Should have thrown");
            Assert.AreEqual("tokenCounts", ex.ParamName, "incorrect param name");
        }

        [Test]
        public void Initialise_NullTemplatePreferencesThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            var ex = Assert.Throws<ArgumentNullException>(() =>
                    itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]),
                        new TokenCounts(), 
                        null), "Should have thrown");
            Assert.AreEqual("templatePreferences", ex.ParamName, "incorrect param name");
        }
        
        [Test]
        public void ProcessTemplate_NullSourceStreamThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();

            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]),
                        new TokenCounts(),
                        new TemplatePreferences());

            var ex = Assert.Throws<ArgumentNullException>(()=> itemUnderTest.ProcessTemplate(null, mockTemplateProcessor.Object, new MemoryStream()), "Exception expected");
            Assert.AreEqual("sourceUnprocessedTemplateStream", ex.ParamName, "Incorrect ParamName");
        }

        [Test]
        public void ProcessTemplate_NullTemplateProcessorThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();

            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]),
                        new TokenCounts(),
                        new TemplatePreferences());

            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.ProcessTemplate(new MemoryStream(), null, new MemoryStream()), "Exception expected");
            Assert.AreEqual("templateProcessor", ex.ParamName, "Incorrect ParamName");
        }

        [Test]
        public void ProcessTemplate_NullSourceTargetThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();

            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]),
                        new TokenCounts(),
                        new TemplatePreferences());

            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.ProcessTemplate(new MemoryStream(), mockTemplateProcessor.Object, null), "Exception expected");
            Assert.AreEqual("targetProcessedTemplateStream", ex.ParamName, "Incorrect ParamName");
        }

        /// <summary>
        /// Asserts the arguments passed to the ProcessTemplate method are correct.
        /// </summary>
        [Test]
        public void ProcessTemplate_CallsTemplateProcessorCorrectly()
        {
            Stream sourceStreamPassedToProcessor = null;
            Stream targetStreamPassedToProcessor = null;

            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();

            mockTemplateProcessor
                .Setup(p => p.ProcessTemplate(It.IsAny<Stream>(), It.IsAny<Stream>(), It.IsAny<MachineConfigurationSettings>()))
                .Callback((Stream inStream, Stream outStream, MachineConfigurationSettings machineSettings) =>
                              {
                                  sourceStreamPassedToProcessor = inStream;
                                  targetStreamPassedToProcessor = outStream;
                              });

            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        new TokenCounts(),
                        new TemplatePreferences());


            itemUnderTest.ProcessTemplate(sourceStream, mockTemplateProcessor.Object, targetStream);

            Assert.AreSame(sourceStream, sourceStreamPassedToProcessor, "sourceStream: incorrect");
            Assert.AreSame(targetStream, targetStreamPassedToProcessor, "targetStream: incorrect");
        }

        /// <summary>
        /// Asserts that events raised by the template processor during ProcessTemplate are correctly recorded.
        /// </summary>
        [Test]
        public void ProcessTemplate_TokenCountEventsRaisedDuringProcess()
        {
            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            mockTemplateProcessor
              .Setup(p => p.ProcessTemplate(It.IsAny<Stream>(), It.IsAny<Stream>(), It.IsAny<MachineConfigurationSettings>()))
              .Callback((Stream inStream, Stream outStream, MachineConfigurationSettings machineSettings) =>
              {
                  mockTemplateProcessor.Raise(p => p.TokenUsed += null, new TokenEventArgs("a"));
                  mockTemplateProcessor.Raise(p => p.TokenUsed += null, new TokenEventArgs("b"));
                  mockTemplateProcessor.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("c"));
                  mockTemplateProcessor.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("d"));
              });

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                        new TemplatePreferences());

            itemUnderTest.ProcessTemplate(sourceStream, mockTemplateProcessor.Object, targetStream);
           
            Assert.AreEqual(2, tokenCounts.TokenUseCounts.Count, "TokenUseCount: incorrect");
            Assert.IsTrue(tokenCounts.TokenUseCounts.ContainsKey("a"), "token a: not found");
            Assert.IsTrue(tokenCounts.TokenUseCounts.ContainsKey("b"), "token b: not found");
            Assert.AreEqual(2, tokenCounts.UnrecognisedTokenCounts.Count, "UnrecognisedTokenCounts: incorrect");
            Assert.IsTrue(tokenCounts.UnrecognisedTokenCounts.ContainsKey("c"), "token c: not found");
            Assert.IsTrue(tokenCounts.UnrecognisedTokenCounts.ContainsKey("d"), "token d: not found");
        }

        /// <summary>
        /// Asserts that events raised by the template processor after ProcessTemplate are not recorded, ie events are unhooked
        /// </summary>
        [Test]
        public void ProcessTemplate_TokenCountEventsNotRaisedAfterProcess()
        {
            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                        new TemplatePreferences());

            itemUnderTest.ProcessTemplate(sourceStream, mockTemplateProcessor.Object, targetStream);
            mockTemplateProcessor.Raise(p => p.TokenUsed += null, new TokenEventArgs("a"));
            mockTemplateProcessor.Raise(p => p.TokenUsed += null, new TokenEventArgs("b"));
            mockTemplateProcessor.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("c"));
            mockTemplateProcessor.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("d"));

            Assert.AreEqual(0, tokenCounts.TokenUseCounts.Count, "TokenUseCount: incorrect");
            Assert.AreEqual(0, tokenCounts.UnrecognisedTokenCounts.Count, "UnrecognisedTokenCounts: incorrect");
        }

        /// <summary>
        /// Asserts that the position of the target stream is reset to zero after any writing from the ProcessTemplate call on the templateProcessor.
        /// </summary>
        [Test]
        public void ProcessTemplate_TargetStreamPositionResetAfterProcess()
        {
            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            mockTemplateProcessor
              .Setup(p => p.ProcessTemplate(It.IsAny<Stream>(), It.IsAny<Stream>(), It.IsAny<MachineConfigurationSettings>()))
              .Callback((Stream inStream, Stream outStream, MachineConfigurationSettings machineSettings) =>
              {
                 outStream.WriteByte(0x01);
              });

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                        new TemplatePreferences());

            itemUnderTest.ProcessTemplate(sourceStream, mockTemplateProcessor.Object, targetStream);

            Assert.AreEqual(0, targetStream.Position, "target stream position not reset");
        }

        [Test]
        public void ReplaceTokens_NullSourceStreamThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();

            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]),
                        new TokenCounts(),
                        new TemplatePreferences());

            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.ReplaceTokens(null, new MemoryStream()), "Exception expected");
            Assert.AreEqual("sourceProcessedTemplateStream", ex.ParamName, "Incorrect ParamName");
        }

        [Test]
        public void ReplaceTokens_NullTargetStreamThrows()
        {
            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();

            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]),
                        new TokenCounts(),
                        new TemplatePreferences());

            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.ReplaceTokens(new MemoryStream(), null), "Exception expected");
            Assert.AreEqual("targetConfigStream", ex.ParamName, "Incorrect ParamName");
        }

        /// <summary>
        /// Asserts the arguments passed to the ReplaceTokens method are correct.
        /// </summary>
        [Test]
        public void ReplaceTokens_LocateTokensCalledCorrectly()
        {
            var sourceStream = new MemoryStream(new byte[] { 0x48, 0x49 });
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();

            TextReader readerPassedToLocator = null;
            Stream baseStreamOnReaderPassedToLocator = null;
            TextWriter writerPassedToLocator = null;
            Stream baseStreamOnWriterPassedToLocator = null;
            string startDelimiterPassedToLocator = null;
            string endDelimiterPassedToLocator = null;

            mockTokenLocator
              .Setup(p => p.LocateTokens(It.IsAny<TextReader>(), It.IsAny<TextWriter>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Func<string, string>>()))
              .Callback((TextReader reader, TextWriter writer, string start, string end, Func<string, string> callback) =>
                            {
                                readerPassedToLocator = reader;
                                if (reader is StreamReader) baseStreamOnReaderPassedToLocator = ((StreamReader)reader).BaseStream;
                                writerPassedToLocator = writer;
                                if (writer is StreamWriter) baseStreamOnWriterPassedToLocator = ((StreamWriter)writer).BaseStream;
                                startDelimiterPassedToLocator = start;
                                endDelimiterPassedToLocator = end;
                            });

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                         new TemplatePreferences { Token = new TokenPreferences{StartDelimiter = "<", EndDelimiter = ">"} });

            itemUnderTest.ReplaceTokens(sourceStream, targetStream);

            Assert.AreEqual("<", startDelimiterPassedToLocator, "Start delimiter: incorrect");
            Assert.AreEqual(">", endDelimiterPassedToLocator, "End delimiter: incorrect");
            Assert.IsNotNull(readerPassedToLocator, "readerPassedToLocator: should not be null");
            Assert.IsTrue(readerPassedToLocator is StreamReader, "readerPassedToLocator: incorrect type");
            Assert.AreSame(sourceStream, baseStreamOnReaderPassedToLocator, "readerPassedToLocator: should wrap targetStream");
            Assert.IsNotNull(writerPassedToLocator, "writerPassedToLocator: should not be null");
            Assert.IsTrue(writerPassedToLocator is StreamWriter, "writerPassedToLocator: incorrect type");
            Assert.AreSame(targetStream, baseStreamOnWriterPassedToLocator, "writerPassedToLocator: should wrap targetStream");
        }

        /// <summary>
        /// Asserts that events raised by the token locator during ReplaceTokens are correctly recorded.
        /// </summary>
        [Test]
        public void ReplaceTokens_TokenCountEventsRaisedDuringReplace()
        {
            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();

            mockTokenLocator
              .Setup(p => p.LocateTokens(It.IsAny<TextReader>(), It.IsAny<TextWriter>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Func<string, string>>()))
              .Callback((TextReader reader, TextWriter writer, string start, string end, Func<string, string> callback) =>
              {
                  mockTokenLocator.Raise(p => p.TokenUsed += null, new TokenEventArgs("a"));
                  mockTokenLocator.Raise(p => p.TokenUsed += null, new TokenEventArgs("b"));
                  mockTokenLocator.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("c"));
                  mockTokenLocator.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("d"));
              });

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                         new TemplatePreferences { Token = new TokenPreferences() });

            itemUnderTest.ReplaceTokens(sourceStream, targetStream);

            Assert.AreEqual(2, tokenCounts.TokenUseCounts.Count, "TokenUseCount: incorrect");
            Assert.IsTrue(tokenCounts.TokenUseCounts.ContainsKey("a"), "token a: not found");
            Assert.IsTrue(tokenCounts.TokenUseCounts.ContainsKey("b"), "token b: not found");
            Assert.AreEqual(2, tokenCounts.UnrecognisedTokenCounts.Count, "UnrecognisedTokenCounts: incorrect");
            Assert.IsTrue(tokenCounts.UnrecognisedTokenCounts.ContainsKey("c"), "token c: not found");
            Assert.IsTrue(tokenCounts.UnrecognisedTokenCounts.ContainsKey("d"), "token d: not found");
        }

        /// <summary>
        /// Asserts that events raised by the token locator after ReplaceTokens are not recorded, ie events are unhooked
        /// </summary>
        [Test]
        public void ReplaceTokens_TokenCountEventsNotRaisedAfterProcess()
        {
            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                         new TemplatePreferences { Token = new TokenPreferences() });

            itemUnderTest.ReplaceTokens(sourceStream, targetStream);
            mockTokenLocator.Raise(p => p.TokenUsed += null, new TokenEventArgs("a"));
            mockTokenLocator.Raise(p => p.TokenUsed += null, new TokenEventArgs("b"));
            mockTokenLocator.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("c"));
            mockTokenLocator.Raise(p => p.UnrecognisedToken += null, new TokenEventArgs("d"));

            Assert.AreEqual(0, tokenCounts.TokenUseCounts.Count, "TokenUseCount: incorrect");
            Assert.AreEqual(0, tokenCounts.UnrecognisedTokenCounts.Count, "UnrecognisedTokenCounts: incorrect");
        }

        /// <summary>
        /// Asserts that data is correctly written to the target stream and the position is reset
        /// </summary>
        [Test]
        public void ReplaceTokens_DataCorrectlyWrittenToTargetAndPositionReset()
        {
            var sourceStream = new MemoryStream();
            var targetStream = new MemoryStream();
            var currentMachine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var mockTokenReplacer = new Mock<ITokenReplacer>();
            var mockTokenLocator = new Mock<ITokenLocator>();
            mockTokenLocator
             .Setup(p => p.LocateTokens(It.IsAny<TextReader>(), It.IsAny<TextWriter>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Func<string, string>>()))
             .Callback((TextReader reader, TextWriter writer, string start, string end, Func<string, string> callback) =>
             {
                    writer.Write("2");
             });

            var tokenCounts = new TokenCounts();
            var itemUnderTest = new SingleFileGenerator(mockTokenLocator.Object, mockTokenReplacer.Object);
            itemUnderTest.Initialise(
                        currentMachine,
                        tokenCounts,
                         new TemplatePreferences { Token = new TokenPreferences() });

            itemUnderTest.ReplaceTokens(sourceStream, targetStream);

            Assert.AreEqual(1, targetStream.Length, "Incorrect length on target stream - data not written or writer not flushed?");
            Assert.AreEqual(0, targetStream.Position, "Incorrect position on target stream - not reset");
            Assert.AreEqual(50, targetStream.ReadByte(), "Incorrect data on stream");
        }
    }
}
